package com.hj.springframework4.io;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.function.Function;

public class ResourceResolver {
    Logger logger = LoggerFactory.getLogger(getClass());

    String basePackage;

    public ResourceResolver(String basePackage) {
        this.basePackage = basePackage;
    }

    /**
     * @param mapper Function函数是传入Resource对象,返回R对象
     * @param <R>
     * @return
     */
    public <R> List<R> scan(Function<Resource, R> mapper) {
        String basePackagePath = this.basePackage.replace(".", "/");
        try {
            List<R> collector = new ArrayList<>();
            scan0(basePackagePath, collector, mapper);
            return collector;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private <R> void scan0(String basePackagePath, List<R> collector, Function<Resource, R> mapper) throws IOException, URISyntaxException {
        logger.info("scan package: " + basePackagePath);
        Enumeration<URL> en = getContextClassLoader().getResources(basePackagePath);
        while (en.hasMoreElements()) {
            URL url = en.nextElement();
            URI uri = url.toURI();
            String uriStr = removeTrailingSlash(uriToString(uri));
            String uriBaseStr = uriStr.substring(0, uriStr.length() - basePackagePath.length());
            if (uriBaseStr.startsWith("file:")) {
                uriBaseStr = uriBaseStr.substring(5);
            }
            if (uriBaseStr.startsWith("jar:")) {
                scanFile(true, uriBaseStr, jarUriToPath(basePackagePath, uri), collector, mapper);
            } else {
                scanFile(false, uriBaseStr, Paths.get(uri), collector, mapper);
            }
        }
    }

    private <R> void scanFile(boolean isJar, String uriBaseStr, Path root, List<R> collector, Function<Resource, R> mapper) throws IOException {
        String baseDir = removeTrailingSlash(uriBaseStr);
        Files.walk(root)// 递归遍历 root 下的所有文件和目录
                .filter(Files::isRegularFile)// 只保留普通文件（排除目录、符号链接等）
                .forEach(file -> {
                    Resource rs;
                    if (isJar) {
                        //baseDir：JAR 文件的 URI（如 jar:file:/path/to/app.jar!/）。
                        //file.toString()：JAR 内部的路径（如 com/example/Main.class）。
                        //removeLeadingSlash：移除路径开头的 /（如果有），避免双斜杠。
                        //最终 Resource 的路径格式可能是：jar:file:/path/to/app.jar!/com/example/Main.class。
                        rs = new Resource(baseDir, removeLeadingSlash(file.toString()));
                    } else {
                        //baseDir：文件目录的绝对路径（如 /home/user/project）。
                        //name：从 baseDir 截取后的相对路径。
                        //最终 Resource 的路径格式是：file:/home/user/project/src/Main.java。
                        String path = file.toString();
                        String name = removeLeadingSlash(path.substring(baseDir.length()));
                        rs = new Resource("file:" + path, name);
                    }
                    logger.info("found resource: {}", rs);

                    R r = mapper.apply(rs);
                    if (r != null) {
                        collector.add(r);
                    }
                });
    }

    String removeLeadingSlash(String path) {
        if (path.startsWith("/") || path.startsWith("\\")) {
            return path.substring(1);
        }
        return path;
    }

    /**
     * 通过 JAR 文件的 URI（jarUri）创建一个虚拟文件系统（FileSystem），然后获取该 JAR 文件内指定路径（basePackagePath）的 Path 对象。
     * 适用于读取 JAR 文件内部的资源（如类文件、配置文件等）
     *
     * @param basePackagePath
     * @param jarUri
     * @return
     * @throws IOException
     */
    Path jarUriToPath(String basePackagePath, URI jarUri) throws IOException {
        return FileSystems.newFileSystem(jarUri, new HashMap<>()).getPath(basePackagePath);
    }

    /**
     * 对rui进行解码
     *
     * @param uri
     * @return
     */
    String uriToString(URI uri) {
        return URLDecoder.decode(uri.toString(), StandardCharsets.UTF_8);
    }

    String removeTrailingSlash(String path) {
        if (path.endsWith("/") || path.endsWith("\\")) {
            return path.substring(0, path.length() - 1);
        }
        return path;
    }

    /**
     * ClassLoader首先从Thread.currentThread().getContextClassLoader()获取,如果获取不到,再从当前类获取.
     * 因为web应用的ClassLoader不是JVM提供的基于classpath的ClassLoader,而是Servlet容器提供的ClassLoader,它不在默认的Classpath搜索,
     * 而是在/WEB-INF/lib和/WEB-INF/classes下查找类.
     *
     * @return
     */
    private ClassLoader getContextClassLoader() {
        ClassLoader cl;
        cl = Thread.currentThread().getContextClassLoader();
        if (cl == null) {
            cl = getClass().getClassLoader();
        }
        return cl;
    }


}
